/**
 * Title:       InitialisationManager.java
 * Copyright:   Copyright (c) 1996-2004 The Agent Factory Working Group. All rights reserved.
 * Licence:     This file is free software; you can redistribute it and/or modify
 *              it under the terms of the GNU Lesser General Public License as published by
 *              the Free Software Foundation; either version 2.1, or (at your option)
 *              any later version.
 *
 *              This file is distributed in the hope that it will be useful,
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *              GNU Lesser General Public License for more details.
 *
 *              You should have received a copy of the GNU Lesser General Public License
 *              along with Agent Factory; see the file COPYING.  If not, write to
 *              the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *              Boston, MA 02111-1307, USA.
 */
package com.agentfactory.platform;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.net.ServerSocket;
import java.net.Socket;

import com.agentfactory.platform.util.Logger;


/**
 * This is the main entry point for the Agent Factory Framework.
 *
 * An instance of the AgentPlatform class is created. The AgentPlatform class,
 * along with an AgentContainer, basic services, and input reading systems
 * comprise the core functionality.
 *
 * The platform must be initialised with an explicit platform configuration
 * file. One global configuration file is not used since it may be useful to run
 * multiple instances of the platform on the same physical platform. If no
 * configuration file is specified, the class will NOT check any default
 * locations since those locations are likely to live outside the agent factory
 * class structure. Instead, start-up scripts may like to provide the address
 * of a default configuration file on a particular platform.
 *
 * The default operational mode of the Agent Factory Framework is a command line
 * driven. Alternativly, command line options can be specified at start-up to
 * turn the platform into a script driven, or server based platform:
 *
 * a) -script <scriptfile>
 *
 * b) -port <portnum>
 *
 * An out parameter can be used to set the system out. The out parameter takes
 * the name of a file to which system.out will be directed, e.g.:
 *
 * -out <output_file>
 *
 * This class is implemented as a singleton to allow all agent factory classes
 * to have a single point of entry in gettng access to the agent platform
 * object.
 *
 * Note, by default, the platform runs with no GUI; If a GUI is required, the
 * platform configuration file should specify one.
 *
 * @author Robert Ross
 */
public class InitialisationManager {
    /**
     * Set the logging level
     */
    private static final int CLASS_LOG_LEVEL = Logger.LEVEL_ERROR;
    
    /**
     * Flag to monitor if a platform configuration file has been provided.
     */
    private boolean configured = false;
    
    /**
     * The Agent Platform that is being run.
     */
    private AgentPlatform thePlatform = null;
    
    /**
     * The name if the agent platform script that is to be interpreted (if any)
     */
    private String script = null;
    
    /**
     * The port number on which a network client should be established
     */
    private String port = null;
    
    /**
     * Flag used to indicate if an execution mode (script or network) has been
     * selected
     */
    private boolean modeSelected = false;
    
    /**
     * Array of strings given as command line arguements.
     */
    private String[] args = null;
    
    /**
     * The name of the platform configuration file.
     */
    private String configFile = null;
    
    /**
     * The unique instance of the InitialisationManager class.
     */
    private static InitialisationManager theInstance = new InitialisationManager();
    
    /**
     * Private basic class constructor.
     */
    private InitialisationManager() {
    }
    
    /**
     * A singleton access method.
     *
     * @return InitialisationManager - the unique instance of the Initialisation
     *         Manager. This object can be used to get a hold on the actual
     *         Agent Factory platform.
     */
    public static InitialisationManager getInstance() {
        return theInstance;
    }
    
    /**
     * Initialise the IniatialisationMethod. This method must be called before
     * the getInstance method should be called by any external code.
     *
     * @param inArgs
     */
    public void initialise(String[] inArgs) {
        this.args = inArgs;
    }
    
    /**
     * Get a reference to the AgentPlatform object.
     *
     * @return AgentPlatform - The unique instance of the agent platform.
     */
    public AgentPlatform getPlatform() {
        return this.thePlatform;
    }
    
    /**
     * Process the input parameters that have been specified on the command
     * line. All parameters will be processed, resulting in error messages,
     * flags being set, or mode selection as appropriate.
     *
     * This method is normally used by the main method, but can also be used
     * by any class that wants to run the initialiser within another class (a
     * test for example).
     */
    public void parseParameters() {
        // iterate through all of the command line arguements
        String nextString = null;
        for (int i = 0; i < args.length; i++) {
            nextString = args[i];
            if (nextString.equals("-version") || nextString.equals( "-v" ) ) {
                printVersion();
            } else if (nextString.equals("-help") || nextString.equals( "-h" ) ) {
                printHelpMesg();
            } else if (nextString.equals("-port") || nextString.equals( "-p" ) ) {
                if (modeSelected == true) {
                    System.out.println("Error - two mode options cannot be chosen");
                    System.exit(1);
                } else {
                    modeSelected = true;
                    i++;
                    this.port = args[i];
                }
            } else if ( nextString.equals("-script") || nextString.equals( "-s" ) ) {
                // handle script mode. Next parameter should be the script name
                if (modeSelected == true) {
                    System.out.println("Error - two mode options cannot be chosen");
                    System.exit(1);
                } else {
                    modeSelected = true;
                    i++;
                    this.script = args[i];
                }
            } else if ( nextString.equals("-config") || nextString.equals( "-c" ) ) {
                // start the platform
                i++;
                this.configFile = args[i];
                configured = true;
            } else if ( nextString.equals( "-out" ) || nextString.equals( "-o" ) ) {

                try {
                    // open a PrintStream to the output file in append mode
                    PrintStream output = new PrintStream( new FileOutputStream( args[ ++i ], true ) );
                    System.setOut( output );
                    System.setErr( output );
                }
                
                catch ( FileNotFoundException e ) {
                    System.err.println( "[InitialisationManager] Error: cannot open output file " + args[ i ] );
                }
            }
        }
    }
    
    /**
     * Print a simple help message to standard out. The message includes
     * command line information and version numbers.
     *
     */
    public void printHelpMesg() {
        printVersion();
        System.out.println("Please See the Agent Factory Manual for Detailed Documentation");
        System.out.println("Basic Usage:");
        System.out.println("     agentfactory -config <config_file> [-script <script_name> | "
                + "-port <port_number> ] [-output <output_file> ]");
    }
    
    /**
     * Print a message with the Agent Factory version number.
     *
     * TODO - robertr - 13-Aug-2004 - Implement some dynamic way of
     * updating the version number.
     */
    public void printVersion() {
        System.out.println("The Agent Factory RTE; Version 1.0.1");
    }
    
    /**
     * Complete the initialisation proceedure by creating a platform and
     * agents.
     *
     * This mehtod should be called after the command line input has been parsed.
     * If not, the method will exit immeditately, complaining that no
     * configuration file has been supplied.
     */
    public void processInput() {
        Logger.enter("InitializationManager:processInput()", CLASS_LOG_LEVEL);
        // Sanity check, we shold have been configured
        if (!configured) {
            // not configuired.
            System.out.println("error - no configuration file was specified");
            System.out.println("A configuration file always needs to be set.");
            System.exit(1);
        }
        
        // Start the Agent Factory Platform.
        thePlatform = new AgentPlatform(configFile);
        
        // Check mode.
        if (modeSelected == true) {
            if (script != null) {
                InputStream in = getClass().getResourceAsStream("/" + script);
                try {
                    new APScriptHandler(in).processScript();
                } catch (Exception e) {
                    Logger.error("Failed to load Script: " + script, CLASS_LOG_LEVEL);
                    e.printStackTrace();
                }
            } else if (port != null) {
                // Try opening a network connect on the specified port
                int iport = Integer.parseInt(port);
                Socket sock = initialiseNetworkInterface(iport);
                
                // set the output stream.to the created network socket
                try {
                    this.setOutputStream(sock.getOutputStream(),sock.getInputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                
                // start processing the resultant input stream.
                new APScriptHandler(System.in).processScript();
            } else {
                System.out.println("should not have reached here");
                System.exit(1);
            }
        } else {
            // No mode was selected. Go into interactive mode.
            new APScriptHandler(System.in).processScript();
        }
    }
    
    /**
     * Initialise a network command interface on a given port number. A server
     * will be started that waits for a client to connect. Once the client has
     * connected, the socket connection will be returned.
     *
     * @param portNumber -
     *            An int primitive that specifies the port on which the server
     *            should be started.
     * @return Socket - A socket to a client which has connected.
     */
    public Socket initialiseNetworkInterface(int portNumber) {
        
        System.out.println("waiting for a network connectioin");
        
        Socket sock = null;
        try {
            ServerSocket s = new ServerSocket(portNumber);
            sock = s.accept();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        System.out.println("got a network connection");
        return sock;
    }
    
    /**
     * Set the default system output stream to the specified output stream. Once
     * this command has been called all system output and system error messages
     * will go to the specified output stream.
     *
     * @param os
     *            An OutputStream object.
     */
    public void setOutputStream(OutputStream os, InputStream is) {
        PrintStream ps = new PrintStream(os, true);
        
        System.setOut(ps);
        //System.setErr(ps);
        System.setIn(is);
    }
    
    /**
     * This is the method that starts all methods!!! An instance of the
     * initialisation class will be created and used to parse the command
     * line parameters. Based on that parsing, the agent platform will be
     * created along with an appropriate input interface handler.
     */
    public static void main(String[] args) {
        InitialisationManager theInitialiser = InitialisationManager.getInstance();
        theInitialiser.initialise(args);
        theInitialiser.parseParameters();
        
        theInitialiser.processInput();
    }
    
}